﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Epsilon
{
    [Serializable]
    public class LexException : Exception
    {
        public LexException() { }
        public LexException(string message) : base(message) { }
        public LexException(string message, Exception inner) : base(message, inner) { }
        protected LexException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context) { }
    }

    public class Lexer
    {
        static StreamReader reader;
        static string[] keywords = new string[] { 
            "and", "andalso", "band", "begin", "bnot", "bool", "bor", "bsl", "bsr", "bxor",
            "case", "element", "end", "false", "fun", "if", "int", "let", "list", "not", "of", "or", "orelse",
            "pid", "process", "receive", "record", "self", "spawn", "spec", "string", "true", "void", "xor"
        };
        static int line = 1;
        static int col = 1;

        public static List<Token> Lex(StreamReader r)
        {
            List<Token> tokens = new List<Token>();
            reader = r;
            line = 1;
            col = 1;

            while (!reader.EndOfStream)
            {
                int c = Peek();
                if ((c >= 'a' && c <= 'z') || c == '\'')
                    tokens.Add(ReadAtom());
                else if (c >= 'A' && c <= 'Z')
                    tokens.Add(ReadVar());
                else if (c >= '0' && c <= '9')
                    tokens.Add(ReadInt());
                else if (c == '"')
                    tokens.Add(ReadString());
                else if (c == '(' || c == ')' || c == '{' || c == '}' || c == '.' || c == ',' || c == ':' || c == ';' ||
                    c == '#' || c == '!' || c == '|' || c == '*' || c == '[')
                    tokens.Add(new Token(TokenType.Symbol, Convert.ToChar(Read()).ToString(), line, col));
                else if (c == '+' || c == '-' || c == '/' || c == ']' || c == '=' || c == '<' || c == '>')
                    tokens.Add(ReadSymbol());
                else if (c == '%')
                    RemoveComment();
                else if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
                    RemoveWhitespace();
                else
                    Read();
            }

            return tokens;
        }

        static int Peek() 
        {
            return reader.Peek();
        }

        static int Read()
        {
            int c = reader.Read();
            if (c == '\n')
            {
                line++;
                col = 1;
            }
            else
            {
                col++;
            }
            return c;
        }

        static void Error(string e, int l, int c)
        {
            string s = e + "\nat line " + l + " col " + c;
            throw new LexException(s);
        }

        static void RemoveWhitespace()
        {
            while (true)
            {
                int c = Peek();
                if (c == '\n' || c == '\t' || c == ' ' || c == '\r')
                    Read();
                else
                    break;
            }
        }

        static void RemoveComment()
        {
            while (true)
            {
                int c = Read();
                if (c == '\n')
                    break;
            }
        }

        static Token ReadSymbol()
        {
            int c = Read();
            string sym = "";

            if (c == '+')
            {
                if (Peek() == '+') { sym = "++"; Read(); }
                else { sym = "+"; }
            }
            else if (c == '-')
            {
                if (Peek() == '-') { sym = "--"; Read(); }
                else if (Peek() == '>') { sym = "->"; Read(); }
                else { sym = "-"; }
            }
            else if (c == '/')
            {
                if (Peek() == '=') { sym = "/="; Read(); }
                else { sym = "/"; }
            }
            else if (c == ']')
            {
                if (Peek() == '>') { sym = "]>"; Read(); }
                else { sym = "]"; }
            }
            else if (c == '=')
            {
                if (Peek() == '=') { sym = "=="; Read(); }
                else if (Peek() == '<') { sym = "=<"; Read(); }
                else { sym = "="; }
            }
            else if (c == '<')
            {
                if (Peek() == '[') { sym = "<["; Read(); }
                else { sym = "<"; }
            }
            else if (c == '>')
            {
                if (Peek() == '=') { sym = ">="; Read(); }
                else { sym = ">"; }
            }
            else
            {
                Error("Invalid symbol", line, col);
            }
            return new Token(TokenType.Symbol, sym, line, col);
        }

        static Token ReadInt()
        {
            List<char> cs = new List<char>();
            while (true)
            {
                int c = Peek();
                if (c >= '0' && c <= '9')
                    cs.Add(Convert.ToChar(Read()));
                else
                    break;
            }
            return new Token(TokenType.Integer, new string(cs.ToArray()), line, col);
        }

        static Token ReadString()
        {
            List<char> cs = new List<char>();

            int ll = line, cc = col;
            Read();
            while (true)
            {
                int c = Read();
                if (c == '\\')
                {
                    cs.Add(Convert.ToChar(c));
                    if (Peek() == -1)
                        Error("Invalid escape sequence", line, col);
                    cs.Add(Convert.ToChar(Read()));
                }
                else
                {
                    if (c == '"')
                        break;
                    else if (c == -1)
                        Error("No end of string terminator", ll, cc);
                    else
                        cs.Add(Convert.ToChar(c));
                }
            }

            return new Token(TokenType.String, new string(cs.ToArray()), line, col);
        }

        static Token ReadAtom()
        {
            List<char> cs = new List<char>();

            if (Peek() == '\'')
            {
                // store where the string starts
                int ll = line, cc = col;
                Read();
                while (true)
                {
                    int c = Read();
                    if (c == '\\')
                    {
                        cs.Add(Convert.ToChar(c));
                        if (Peek() == -1)
                            Error("Invalid escape sequence", line, col);
                        cs.Add(Convert.ToChar(Read()));
                    }
                    else
                    {
                        if (c == '\'')
                            break;
                        else if (c == -1)
                            Error("No end of string terminator", ll, cc);
                        else
                            cs.Add(Convert.ToChar(c));
                    }
                }

                return new Token(TokenType.Atom, new string(cs.ToArray()), line, col);
            }
            else
            {
                while (true)
                {
                    int c = Peek();
                    if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '@' || c == '_')
                        cs.Add(Convert.ToChar(Read()));
                    else
                        break;
                }

                string s = new string(cs.ToArray());
                // Now check if it is a keyword or atom
                if (Array.IndexOf(keywords, s) >= 0)
                    return new Token(TokenType.Keyword, s, line, col);
                else
                    return new Token(TokenType.Atom, s, line, col);
            }
        }

        static Token ReadVar()
        {
            List<char> cs = new List<char>();
            while (true)
            {
                int c = Peek();
                if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '@' || c == '_')
                    cs.Add(Convert.ToChar(Read()));
                else
                    break;
            }
            return new Token(TokenType.Variable, new string(cs.ToArray()), line, col);
        }
    }
}
